using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace Net.SamuelChen.Libs.WinForm.Calendar {
	internal enum DaysAlignment {
		TopLeft = 0,
		TopRight = 1,
		BottomLeft = 2,
		BottomRight = 3,
	} ;

	public delegate void CellClickHandler(CalendarMouseEventArgs e);

	public delegate void CellDoubleClickHandler(CalendarMouseEventArgs e);

	public delegate void DateChangedHandler(EventArgs e); 
	public delegate void MonthChangedHandler(EventArgs e); 

	/// <summary>
	/// Summary description for MonthView.
	/// </summary>
	public class MonthView : Panel {
		/// <summary>
		/// Required designer variable.
		/// </summary>
		
		private const int maxRows = 6, maxCols = 7;
		private const int hCellPadding = 2, vCellPadding = 3;

		private Container components = null;

		private Array cells = Array.CreateInstance(typeof (MonthViewCell), maxRows, maxCols);
		private DateTime _activeDate = DateTime.Today;
		private Font _headerFont = new Font("Verdana", 8);
		private Font _daysFont = new Font("Verdana", 8);
		private HorizontalAlignment _headerAlign = HorizontalAlignment.Center;
		private DaysAlignment _daysAlign = DaysAlignment.BottomLeft;
		

		//[System.ComponentModel.DesignerCategory ("Action")]
		public event CellClickHandler CellClick;

		//[System.ComponentModel.DesignerCategory("Action")]
		public event CellDoubleClickHandler CellDoubleClick;

		public event DateChangedHandler DateChanged;
		public event MonthChangedHandler MonthChanged;


		internal MonthView() {
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			// Appearance
			BorderStyle = BorderStyle.Fixed3D;

			SetStyle(ControlStyles.Selectable, true);
			SetStyle(ControlStyles.ResizeRedraw, true);
			//SetStyle(ControlStyles.DoubleBuffer, true);

			_activeDate = DateTime.Now;
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose(bool disposing) {
			if (disposing) {
				if (components != null)
					components.Dispose();
			}
			base.Dispose(disposing);
		}

		#region Component Designer generated code

		/// <summary>
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent() {}

		#endregion

		#region events

		protected override void OnPaint(PaintEventArgs pe) {
			Graphics graph = pe.Graphics;
			int w = pe.ClipRectangle.Width/maxCols;
			int h = pe.ClipRectangle.Height/(maxRows+1);

			int x = 0, y = 0;
			string dayName = null;
			string cultureLang = Thread.CurrentThread.CurrentUICulture.Name.Substring(0, 2).ToUpper();

			// draw header
			for (int i = 0; i < maxCols; i++) {
				graph.FillRectangle(SystemBrushes.InactiveCaption, x, y, w, h);

				// show day name in different way
				dayName = Thread.CurrentThread.CurrentUICulture.DateTimeFormat.DayNames[i];
				if (w > 40)
					graph.DrawString(dayName, HeaderFont, SystemBrushes.HighlightText, x + hCellPadding, y + vCellPadding);
				else {
					if (cultureLang.Equals("ZH"))
						graph.DrawString(dayName.Substring(2, 1), HeaderFont, SystemBrushes.HighlightText, x + hCellPadding, y + vCellPadding);
					else if (cultureLang.Equals("EN") || cultureLang.Equals("FR") || cultureLang.Equals("JP") || cultureLang.Equals("KR"))
						graph.DrawString(dayName.Substring(0, 1), HeaderFont, SystemBrushes.HighlightText, x + hCellPadding, y + vCellPadding);
					else
						graph.DrawString(dayName, HeaderFont, SystemBrushes.HighlightText, x + hCellPadding, y + vCellPadding);
				}
				x += w;
			}
			graph.FillRectangle(SystemBrushes.InactiveCaption, x, y, w, h);

			// show all cells of days of active month 
			MonthViewCell cell = null;
			for (int i = 0; i < maxRows; i++) {
				for (int j = 0; j < maxCols; j++) {
					cell = (MonthViewCell) cells.GetValue(i, j);
					if (ActiveDate.Day == cell.Day) {
						graph.FillRectangle(SystemBrushes.Highlight, cell.ClientRect);
						graph.DrawString(cell.Day.ToString(), HeaderFont, SystemBrushes.HighlightText, cell.DayPosition);
					} else if (cell.Day > -1) {
						graph.DrawString(cell.Day.ToString(), HeaderFont, SystemBrushes.ControlText, cell.DayPosition);
					}

					// draw marks
					if (cell.Marked) {
//						graph.DrawLine(new Pen(cell.MarkColor, 2), cell.ClientRect.Left+3,
//						               cell.ClientRect.Bottom-10, cell.ClientRect.Left+13, cell.ClientRect.Bottom-10);
						Point[] pts = new Point[3];
						pts[0].X = cell.ClientRect.Right - 7;
						pts[0].Y = cell.ClientRect.Bottom - 2;
						pts[1].X = cell.ClientRect.Right - 2;
						pts[1].Y = cell.ClientRect.Bottom - 7;
						pts[2].X = cell.ClientRect.Right - 2;
						pts[2].Y = cell.ClientRect.Bottom - 2;
						graph.DrawPolygon(new Pen(cell.MarkColor, 2), pts);
					}
				}
			}

			// Calling the base class OnPaint
			base.OnPaint(pe);
		}

		protected override void OnResize(EventArgs e) {
			CalculateCells(true);
			base.OnResize(e);
		}

		protected override void OnMouseDown(MouseEventArgs e) {
			int w = Width/maxCols;
			int h = Height/(maxRows + 1);

			int j = e.X/w;
			int i = e.Y/h - 1;
			if (i < 0 || i >= maxRows || j >= maxCols)
				return;

			MonthViewCell cell = (MonthViewCell) cells.GetValue(i, j);
			if (cell.Day < 0)
				return;

			ActiveDate = new DateTime(ActiveDate.Year, ActiveDate.Month, cell.Day);
			this.Invalidate();

			// raise CellClick event
			CalendarMouseEventArgs evt = new CalendarMouseEventArgs(e, cell, ActiveDate);
			if (e.Clicks > 1) {
				if (CellDoubleClick != null) CellDoubleClick(evt);
			} else {
				if (CellClick != null) CellClick(evt);
			}
			base.OnMouseDown(e);
		}

		#endregion

		#region public methods

		public override void Refresh() {
			this.Invalidate();
		}

		public void Mark(int day) {
			if (day < 1 || day > DateTime.DaysInMonth(this.ActiveDate.Year, this.ActiveDate.Month))
				return;

			DateTime theDay = new DateTime(this.ActiveDate.Year, this.ActiveDate.Month, day);
			DateTime firstDay = new DateTime(this.ActiveDate.Year, this.ActiveDate.Month, 1);

			int i = (int)(day+firstDay.DayOfWeek)/(maxCols+1);
			int j = (int)theDay.DayOfWeek;
			MonthViewCell cell = (MonthViewCell) this.cells.GetValue(i ,j);
			cell.Marked = true;
			cell.MarkColor = Color.LightSkyBlue;
			//this.Invalidate(cell.ClientRect);
		}

		#endregion

		#region Properties

		/* --------------------------
		 * ----- Properties ---------
		 * --------------------------
		 */

		public DateTime ActiveDate {
			get { return _activeDate; }
			set {
				int oldMonth = _activeDate.Month;
				int oldYear = _activeDate.Year;
				_activeDate = value;
				if (DateChanged != null)
					DateChanged(new EventArgs());
				if (MonthChanged != null && (oldMonth != value.Month || oldYear != value.Year)) {
					CalculateCells(false);
					MonthChanged(new EventArgs());
				}
			}
		}

		internal Font HeaderFont {
			get { return _headerFont; }
			set { _headerFont = value; }
		}

		internal Font DaysFont {
			get { return _daysFont; }
			set { _daysFont = value; }
		}

		internal HorizontalAlignment HeaderAlign {
			get { return _headerAlign; }
			set { _headerAlign = value; }
		}

		internal DaysAlignment DaysAlign {
			get { return _daysAlign; }
			set { _daysAlign = value; }
		}

		#endregion properties

		#region Private methods

		/* -----------------------------
		 * ----- private methods -------
		 * -----------------------------
		 */

//		private void CalculateCells() {
//			CalculateCells(false);
//
//			// calendar cells
////			int w = Size.Width/maxCols;
////			int h = Size.Height/maxRows;
////			int x = 0;
////			int y = h;
////
////			DateTime firstDay = new DateTime(this.ActiveDate.Year, this.ActiveDate.Month, 1);
////			int firstDayCell = (int) firstDay.DayOfWeek; // the first day in the cell
////			int lastDayCell = firstDayCell + DateTime.DaysInMonth(ActiveDate.Year, ActiveDate.Month); // the cell last day in
////			int day = 1; // day counter
////			int counter = 0;
////
////			// calculate cell position & day of the month
////			MonthViewCell cell = null;
////			for (int i = 0; i < maxRows; i++) {
////				x = 0;
////				for (int j = 0; j < maxCols; j++) {
////					cell = new MonthViewCell();
////					cell.ClientRect = new Rectangle(x, y, w, h);
////					cell.DayPosition = new Point(x + hCellPadding, y + vCellPadding);
////					if (counter >= firstDayCell && counter < lastDayCell) {
////						cell.Day = day;
////						day ++;
////					} else
////						cell.Day = -1;
////					cells.SetValue(cell, i, j);
////
////					x += w;
////					counter ++;
////				} // for j
////				y += h;
////			} // for i
//		}

		private void CalculateCells(bool keepMarks) {
			// calendar cells
			int w = Size.Width/maxCols;
			int h = Size.Height/(maxRows + 1);
			int x = 0;
			int y = h;

			DateTime firstDay = new DateTime(this.ActiveDate.Year, this.ActiveDate.Month, 1);
			int firstDayCell = (int) firstDay.DayOfWeek; // the first day in the cell
			int lastDayCell = firstDayCell + DateTime.DaysInMonth(ActiveDate.Year, ActiveDate.Month); // the cell last day in
			int day = 1; // day counter
			int counter = 0;

			// calculate cell position & day of the month
			MonthViewCell cell = null;
			MonthViewCell oldCell = null;
			for (int i = 0; i < maxRows; i++) {
				x = 0;
				for (int j = 0; j < maxCols; j++) {
					cell = new MonthViewCell();
					if (keepMarks)
						oldCell = (MonthViewCell) cells.GetValue(i,j);
					cell.ClientRect = new Rectangle(x, y, w, h);
					cell.DayPosition = new Point(x + hCellPadding, y + vCellPadding);
					if (counter >= firstDayCell && counter < lastDayCell) {
						cell.Day = day;
						day ++;
					} else
						cell.Day = -1;
					if (keepMarks && oldCell != null) {
						cell.Marked = oldCell.Marked;
						cell.MarkColor = oldCell.MarkColor;
					}
					cells.SetValue(cell, i, j);

					x += w;
					counter ++;
				} // for j
				y += h;
			} // for i			
		}

		#endregion Private methods
	} // class
} // namespace